package q2d.server;
import java.io.IOException;
import java.net.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import q2d.common.CollisionDetector;
import q2d.common.models.GameContainer;
import q2d.common.models.HitsContainer;
import q2d.common.models.Map;
import q2d.common.models.units.Bullet;
import q2d.common.models.units.Flag;
import q2d.common.models.units.Hit;
import q2d.common.models.units.Player;
import q2d.common.models.units.PlayerHit;
import q2d.common.models.units.WallHit;
import q2d.netWrap.*;

/* GameServer: Starts a new GameServer loop thread */
public class GameServer {
	private static int serverPort;
	public static final int defaultServerPort = 7777;
	private static int playerPort;
	public static final int defaultPlayerPort = 7778;
	
	public static void main( String[] args ) {
		if( args.length >= 2) {
			serverPort = Integer.valueOf(args[2]);
			playerPort = Integer.valueOf(args[3]);
		}
		else {
			serverPort = defaultServerPort;
			playerPort = defaultPlayerPort;
		}
		GameServerLoop gsl = new GameServerLoop( serverPort, playerPort );
		Thread t = new Thread(gsl);
		t.start();
	}
}
/* GameServerLoop: This class is responsible for all
 * GameServer behavior. It is run in new thread.
 */
class GameServerLoop implements Runnable {
	
	//------------------------------------------------------------------------
	// Immortality time.
	private static final int hpLockTime = 100;
	// Immortality counter. Counts from 0 to gpLockTime.
	private int hpLockCounter = 0;
	// Immortality switch.
	private boolean hpLock = false;
	// Get game map.
	private Map gameMap = new Map();
	// Flags & Scores
	private Flag redFlag;
	private	Flag blueFlag;
	private int redTeamScore;
	private int blueTeamScore;
	// Quit condition for branching.
	private static final int quit = 1;
	// Database for GamePlayers in game.
	private ArrayList<GamePlayer> players = new ArrayList<GamePlayer>();
	// Port with default value.
	private int serverPort;
	private int playerPort;
	//------------------------------------------------------------------------
	public GameServerLoop( int sport, int pport ) {
		this.serverPort = sport;
		this.playerPort = pport;
	}
	public void run() {
		// Initialize network control
		Ncontroller nc = new Ncontroller( serverPort, 1);
		nc.listen();
		// For received packets
		Npacket recv = null;
		// For received wrappers
		Nwrapper wrapper = null;
		GameContainer recvCont = null;
		InetAddress address = null;
		// For thread quitting
		int branch = 0;
		int playerIndex = 0;
		boolean playerKnown;
		GamePlayer player = null;
		
		redFlag = new Flag();
		blueFlag = new Flag();
		redFlag.team = Player.RED_TEAM;
		redFlag.team = Player.BLUE_TEAM;
		setFlagHome(redFlag);
		setFlagHome(blueFlag);
		redTeamScore = 0;
		blueTeamScore = 0;
		
		// Server welcome message
		System.out.println("Hello server.");
		
		// Server loop start
		while(branch != quit) {
			try {
				// Get packet from queue.
				recv = nc.pop();
				if(recv != null) {
					address = recv.getPacket().getAddress();
					wrapper = (Nwrapper)nc.retrieveObject(recv);
					
					switch( wrapper.getType() ) {
						// For current player.
						case 1 : {	
							recvCont = (GameContainer)wrapper.getObject();
							player = new GamePlayer(recvCont, address);

							playerKnown = false;
							for(int i = 0; i < players.size(); ++i) {
								if(players.get(i).address.equals(address)) {
									playerKnown = true;
									playerIndex = i;
								}
							}
							if(!playerKnown) { 
								players.add(player);
							}
							else {
								players.get(playerIndex).update(player);
							}	
							break;
						}
						default : {	
							// Unknown packet
						}
					}	
					// Lock hp if needed
					if(hpLock == true) {
						hpLockCounter++;
						if(hpLockCounter == hpLockTime) {
							hpLock = false;
							hpLockCounter = 0;
						}
					}		
					//---------------------------------------------------------
					// Construct container
					//---------------------------------------------------------
					// Count bullets in game.
					int bulletsInGame = 0;
					for( GamePlayer gPlayer : players) {
						bulletsInGame += gPlayer.bullets.size();
					}
					
					for( GamePlayer gPlayer : players) {
						// Collision check for bullets.
						CollisionDetector cd = new CollisionDetector();
						GameContainer sendContainer = new GameContainer(bulletsInGame, players.size() - 1);
						for(int i = 0; i < gPlayer.bullets.size(); ++i){
							Bullet bullet = gPlayer.bullets.get(i);		//JUZ
							bullet.x += bullet.vx;
							bullet.y += bullet.vy;	
							for( GamePlayer playerHit : players) {
								if(cd.isBulletPlayerCollision(bullet.x, 
																bullet.y,
																bullet.vx,
																bullet.vy,
																playerHit.player.x,
																playerHit.player.y )){
									// Manage hit points only if not Immortal
									if(hpLock == false) {
										playerHit.hp -= bullet.power;
										// Manage player death.
										if( playerHit.hp < 0) {
											// Flag returns to base.
											if (playerHit.player.team == Player.BLUE_TEAM && redFlag.isInHome == false && redFlag.owner == playerHit.address){
												// Flag was red.
												setFlagHome(redFlag);
											} else if (playerHit.player.team == Player.RED_TEAM && blueFlag.isInHome == false && blueFlag.owner == playerHit.address){
												// Flag was blue.
												setFlagHome(blueFlag);
											}
											// Player died.
											playerHit.deathCount++;
											// Revive player.
											playerHit.hp = playerHit.maxHP;
											hpLock = true;
										}
										// Send bullet-player collision information.
										HitsContainer hitsContainer = new HitsContainer(1,0);
										hitsContainer.addPlayerHit(new PlayerHit((int)playerHit.player.x, (int)playerHit.player.y));
										for( GamePlayer gp : players) {
											nc.sendObject(hitsContainer, 2, gp.address, playerPort);
										}
										gPlayer.bullets.remove(i);
										break;
										
										//i = Math.max(i-1, 0);
									}
								} 
								else {
									// Wall collision check.
									float collisionPoint = cd.isBulletMapCollision(gameMap, bullet.x, bullet.y, bullet.vx, bullet.vy); 
									if (collisionPoint >= 0) {
										// Send bullet-wall collision information.
										HitsContainer hitsContainer = new HitsContainer(0,1);
										hitsContainer.addWallHit(new WallHit((int)bullet.x,
																(int)bullet.y,
																 bullet.vx,
																 bullet.vy));
										for( GamePlayer gp : players) {
											nc.sendObject(hitsContainer, 2, gp.address, playerPort);
										}
										gPlayer.bullets.remove(i);
										break;
									}
								}
							}
						}
						// Check collision with foreign flag
						if (gPlayer.player.team == Player.BLUE_TEAM && redFlag.isInHome){
							// Blue team player, red flag in base.
							if (cd.isPlayerFlagCollision(redFlag.x, redFlag.y, gPlayer.player.x, gPlayer.player.y)){
								redFlag.isInHome = false;
								redFlag.owner = gPlayer.address;
							}
						} else if (gPlayer.player.team == Player.RED_TEAM && blueFlag.isInHome){
							// Red team player, blue flag in base.
							if (cd.isPlayerFlagCollision(blueFlag.x, blueFlag.y, gPlayer.player.x, gPlayer.player.y)){
								blueFlag.isInHome = false;
								blueFlag.owner = gPlayer.address;
							}
						}
						// Prepare Container
						//---------------------------------------------------------------------------------------------
						// Update Container with bullets.
						for( GamePlayer gp : players ) {
							for(int i = 0; i < gp.bullets.size(); ++i){
								sendContainer.addBullet(gp.bullets.get(i));
							}
						}
						// Update Container with players.
						for( GamePlayer other : players ) {
							if(!gPlayer.address.equals(other.address)){
								sendContainer.addPlayer(other.player);
							}
						}
						// Update game player parameters
						sendContainer.playerHP = gPlayer.hp;
						sendContainer.playerDeathCounter = gPlayer.deathCount;
						// Update flags location.
						if (gPlayer.player.team == Player.BLUE_TEAM && redFlag.isInHome == false && redFlag.owner == gPlayer.address){
							// If current player had flag.
							redFlag.x = gPlayer.player.x;
							redFlag.y = gPlayer.player.y;
						}
						if (gPlayer.player.team == Player.RED_TEAM && blueFlag.isInHome == false && blueFlag.owner == gPlayer.address){
							// If current player had flag.
							blueFlag.x = gPlayer.player.x;
							blueFlag.y = gPlayer.player.y;
						}
						// Update container with flag information
						sendContainer.redFlagX = redFlag.x;
						sendContainer.redFlagY = redFlag.y;
						sendContainer.blueFlagX = blueFlag.x;
						sendContainer.blueFlagY = blueFlag.y;
						sendContainer.blueTeamScore = blueTeamScore;
						sendContainer.redTeamScore = redTeamScore;
						// send container
						//-----------------------------------------------------
						nc.sendObject(sendContainer, 1, gPlayer.address, playerPort);
					}
					// Print server information
					System.out.println("players");
					for(GamePlayer gp : players) {
						System.out.print(gp.address);
						System.out.print(" ");
						System.out.print(gp.player.nick);
						System.out.print(" ");
						System.out.print(gp.deathCount);
						System.out.print(" ");
						System.out.print(gp.hp);
						System.out.println(" ");
					}
				}
				else {
					// Packet is empty.
				}
				// Manage players on server
				//-----------------------------------------------------------------
				for(int i = 0; i < players.size(); ++i) {
					players.get(i).incTime();
					if(players.get(i).getTimeout() > 100) {
						//Manage flag
						if (players.get(i).player.team == Player.BLUE_TEAM && redFlag.isInHome == false && redFlag.owner == players.get(i).address){
							// Flag was red.
							setFlagHome(redFlag);
						} else if (players.get(i).player.team == Player.RED_TEAM && blueFlag.isInHome == false && blueFlag.owner == players.get(i).address){
							// Flag was blue.
							setFlagHome(blueFlag);
						}
						//If player was _absent_ for long enough - remove him.
						players.remove(i);
						i--;
					}
				}
				//-----------------------------------------------------------------
				// Flag collision : points management.
				CollisionDetector cd = new CollisionDetector();
				if (cd.isFlagFlagCollision(redFlag.x, redFlag.y, blueFlag.x, blueFlag.y)){
					if (blueFlag.isInHome && !redFlag.isInHome){
						// Blue flag in base, red not.
						blueTeamScore++;
						setFlagHome(blueFlag);
						setFlagHome(redFlag);
					} else if (!blueFlag.isInHome && redFlag.isInHome){
						// Red flag in base, blue not.
						redTeamScore++;
						setFlagHome(blueFlag);
						setFlagHome(redFlag);
					}
				}
				Thread.sleep(1);
			}catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				// Close Ncontroller
				nc.close();
			}
		}
	}
	/**
	 * Set flag location in mother base.
	 * @param flag
	 */
	private void setFlagHome(Flag flag){
		if (flag.team == Player.BLUE_TEAM){
			flag.x = Map.BLUE_FLAG_HOME_X;
			flag.y = Map.BLUE_FLAG_HOME_Y;
		} else {
			flag.y = Map.RED_FLAG_HOME_Y;
			flag.x = Map.RED_FLAG_HOME_X;
		}
		flag.isInHome = true;
		flag.owner = null;
	}
}
	